<?php

namespace Gnway\Ticket\Rest\Mapping;

use Gnway\Ticket\Rest\Annotation\Cache\AnnotationCacheTemplate;
use Gnway\Ticket\Rest\Annotation\ControllerScan;
use Gnway\Ticket\Rest\Annotation\Version;
use Gnway\Ticket\Rest\Facades\Property;
use Gnway\Ticket\Rest\Facades\Route;
use Gnway\Ticket\Rest\Util\AnnotationScannerUtils;
use Gnway\Ticket\Rest\Util\ClassUtils;
use ReflectionException;

class AnnotationMappingManager
{

  /**
   * Fetch Annotation Cache
   *
   * @param string $key
   * @return AnnotationCacheTemplate|null
   */
  protected function getAnnotationCache(string $key): ?AnnotationCacheTemplate
  {
    if ($key === '' || !extension_loaded('apcu')) {
      return NULL;
    }
    $key = md5($key);
    $template = apcu_fetch($key);
    if ($template instanceof AnnotationCacheTemplate) {
      return $template;
    }

    return NULL;
  }

  /**
   * Set Annotation Cache
   *
   * @param string $key
   * @param AnnotationCacheTemplate $template
   * @return void
   */
  protected function setAnnotationCache(string $key, AnnotationCacheTemplate $template): void
  {
    if ($key === '' || !extension_loaded('apcu')) {
      return;
    }

    apcu_store(md5($key), $template);
  }

  /**
   * Scan Annotation & Load Mappings
   *
   * @param string $class
   * @return void
   * @throws ReflectionException
   */
  public function loadMapping(string $class): void
  {
    $classFile = ClassUtils::getClassFile($class);
    $version = ClassUtils::getAnnotation($class, Version::class);

    //Has annotation cache?
    if ($classFile !== '' && is_object($version) && $version->getValue() !== ''
      && ($cached = $this->getAnnotationCache($classFile))
      && $cached->getVersion() === $version->getValue()) {
      Route::getFacadeRoot()->copy($cached->getRouterMapping());
      Property::getFacadeRoot()->copy($cached->getResourceMapping());

      return;
    }

    //Scan controllers
    $packages = ClassUtils::getAnnotation($class, ControllerScan::class);
    if ($packages) {
      $controllers = AnnotationScannerUtils::scanController($packages->getPackages(), $packages->getExtension());
    } else {
      $controllers = AnnotationScannerUtils::scanController([dirname($classFile)]);
    }

    //Scan method request mappings
    $requestMappings = AnnotationScannerUtils::scanRequestMapping($controllers);
    if ( ! $requestMappings) {
      return;
    }
    $router = Route::getFacadeRoot();
    foreach ($requestMappings as $mapping) {
      $router->group($mapping['prefix'], function () use ($mapping, $router) {
        $router->addRoute($mapping['method'], $mapping['mapping'], [$mapping['class'], $mapping['action']]);

        $mapping['validation'] && $router->validation($mapping['validation']);
      }, $mapping['suffix']);
    }

    //Scan controllers properties
    $resourceMappings = AnnotationScannerUtils::scanPropertiesResource($controllers);
    if ($resourceMappings) {
      $propertyMap = Property::getFacadeRoot();
      foreach ($resourceMappings as $type => $mapping) {
        foreach ($mapping as $property => $value) $propertyMap->bind($type, $property, $value);
      }
    }

    //Do cache
    if (is_object($version) && $version->getValue() !== '' && $classFile !== '') {
      $cached = new AnnotationCacheTemplate();
      $cached->setVersion($version->getValue());
      $cached->setRouterMapping($router);
      isset($propertyMap) && $cached->setResourceMapping($propertyMap);
      $this->setAnnotationCache($classFile, $cached);
    }
  }
}